package host.routingProtocol;

import homeMadeExceptions.NetworkException;
import homeMadeExceptions.ScatterNetException;
import host.RoutingNetworkSocket;

import java.util.HashMap;
import java.util.Vector;

import datatypes.BluetoothDeviceAddress;
import datatypes.Host;
import datatypes.LinkMode;
import datatypes.Message;
import datatypes.RoutingObject;

public class FloodingPublishRouting extends RoutingNetworkSocket {
	
	private final int INNERCLOCKSCANINTERVALL = 3;
	private int innerClock = 0;
	
	
	private enum requestState {
		OPEN, CLOSED, FAILED;
	}
	
	private Vector<BluetoothDeviceAddress> scanCache = null;
	private HashMap<String, requestState> requestMap = new HashMap<String, requestState>();
	
	private Vector<Integer> sendMessages = new Vector<Integer>();
	
	private HashMap<Integer,Integer> msgIDtoVectorPos = new HashMap <Integer, Integer>();
	private HashMap<Integer,Integer> msgIDtoNumberLostMsg = new HashMap <Integer, Integer>();

	private FloodingPublishRouting(Host host) {
		super(host);
	}

	public static FloodingPublishRouting getSocket(Host host){
		return new FloodingPublishRouting(host);
	}
	
	@Override
	public void enableBluetoothDevice (){
		super.enableBluetoothDevice();
		super.hciConfig_setLM(LinkMode.NONE);
		try {
			super.getClientSocket();
			super.getServerSocket();
		} catch (NetworkException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.getDeviceListing();
	}

	@Override
	public void disableBluetoothDevice() {
		super.disableBluetoothDevice();
	}

	@Override
	public void getClientSocket() {
		// nothing to do because is not changed
	}

	@Override
	public void getServerSocket() {
		// nothing to do because is not changed
		
	}

	@Override
	public void getDeviceListing() {
		super.getDeviceListing();
	}

	@Override
	public void onInquiryScanAnswer(Vector<BluetoothDeviceAddress> answer) {
		if (this.scanCache == null){
			this.scanCache = answer;
			//try to connect every node
			for (BluetoothDeviceAddress remote: scanCache){
				try {
					this.requestMap.put(remote.getAddress(), requestState.OPEN);
					rfcomm_connect_to_server(remote);
				} catch (ScatterNetException e) {
					e.printStackTrace();
				}
			}			
		} else {
			for (BluetoothDeviceAddress nA : answer){
				if (this.requestMap.get(nA.getAddress()) == null){
					try {
						rfcomm_connect_to_server(nA);
						this.requestMap.put(nA.getAddress(), requestState.OPEN);
					} catch (ScatterNetException e) {
						e.printStackTrace();
					}					
				}
			}
		}
	}

	@Override
	public void rfcommConnectionRequestReply(BluetoothDeviceAddress pairingPartner, boolean success) {
		if (success){
			this.requestMap.remove(pairingPartner.getAddress());		
			this.requestMap.put(pairingPartner.getAddress(), requestState.CLOSED);
			this.host.getDiscoverySocket().newConnectionEstablished(pairingPartner);
		} else {
			this.requestMap.remove(pairingPartner.getAddress());
			this.requestMap.put(pairingPartner.getAddress(), requestState.FAILED);
		}
	}
	
	private boolean isRequestOpen (){
		return this.requestMap.containsValue(requestState.OPEN);
	}

	@Override
	public void newRemoteHostConnected(BluetoothDeviceAddress remoteHost) {
		//nothing to do inspite of inform the discovery-Part 
		//--> null because discoveryProtocol only response for initiated Connections (rfcommConnectionRequestReply)
		this.host.getDiscoverySocket().newConnectionEstablished(null);
	}

	@Override
	public void invokeMsgAckSignal(Message msg, BluetoothDeviceAddress target) {
		// fine, nothing to do		
	}

	@Override
	public void invokeMsgLostSignal(BluetoothDeviceAddress target, Message msg) {
		//Resent Message (3 times)
		//this message was first time sent before
		if (this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()) != null && 
				this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()).intValue() == 3){
			//do nothing (Message will not send)
		} else if (this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()) == null){
			this.msgIDtoNumberLostMsg.put(msg.getMsgID_asInteger(), new Integer(1));
			super.invokeSendMessage(msg, target);				
		} else if (this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()) != null && 
			this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()).intValue() < 3){
			int lastIndex = this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()).intValue();
			lastIndex ++;
			this.msgIDtoNumberLostMsg.remove(msg.getMsgID_asInteger());
			this.msgIDtoNumberLostMsg.put(msg.getMsgID_asInteger(), new Integer(lastIndex));
			super.invokeSendMessage(msg, target);
		}
	}

	@Override
	public void notificationAPI() {
		//simply count up inner clock
		this.innerClock ++;
		if (!this.isRequestOpen()){
			if (this.innerClock % INNERCLOCKSCANINTERVALL == 0){
				startScanAgain ();
			}
		}
	}

	private void startScanAgain() {
		this.getDeviceListing();
	}

	@Override
	public void invokeSendMessage(Message msg, BluetoothDeviceAddress target) {
		Vector<BluetoothDeviceAddress> connections = super.rfcomm_show();
		//message startet locally from discovery Part
		if (msg.isDiscoveryMessage()){
			//target == null if no specific host for request is given, for example for find a service
			//therefore target is null
			if (target == null){
				RoutingObject tmp = new RoutingObject(target, msg);
				tmp.addHopToRoute(host.getBd_Addr());
				Message msgForSend = new Message (tmp, false);
				for (BluetoothDeviceAddress connection : connections){
					super.invokeSendMessage(msgForSend, connection);
				}					
			} else {
				RoutingObject tmp = new RoutingObject(target, msg);
				tmp.addHopToRoute(host.getBd_Addr());
				Message msgForSend = new Message (tmp, false);
				super.invokeSendMessage(msgForSend, target);	
			}
		} 
		//Message is forwarded by flooding
		else {
			//this message was never sent before
			if (msgIDtoVectorPos.get(msg.getMsgID_asInteger()) == null){
				this.sendMessages.add(msg.getMsgID_asInteger());
				this.msgIDtoVectorPos.put(msg.getMsgID_asInteger(), this.sendMessages.size()-1);
				for (BluetoothDeviceAddress connection : connections){
					super.invokeSendMessage(msg, connection);
				}				
			}
		}
		
	}

	@Override
	public void onReceiveMessage(Message msg, BluetoothDeviceAddress sender) {
		if (msg.isRoutingMessage()){
			RoutingObject tmp = (RoutingObject)msg.getContent();
			//I'm the Target (im the targent to, if target is null == broadcast)
			if (tmp.getTarget() == null || tmp.getTarget().getAddress().equals(this.host.getBd_Addr().getAddress())){
				//Message with Discovery Protocol Informations
				if (tmp.getDiscoveryMessage() != null){
					super.onReceiveMessage(tmp.getDiscoveryMessage(), tmp.getInitialSender());
				} 		
			} 
			//I'm not the Target
			else {
				//add me to route an send to next hop
				tmp.addHopToRoute(host.getBd_Addr());
				//target is unimportant, because already stored in msg and flooding is for send to all other
				this.invokeSendMessage(msg, null);
			}
		} else {
			//Must be an other protocol, the host don't understand
			//this method sends the Received Message to discovery "Layer" if it is an discoveryMessage
			//super.onReceiveMessage(msg, sender);	
		}		
	}

	
}
